Mybatis 您所在的位置:网站首页 mock resultset Mybatis

Mybatis

#Mybatis| 来源: 网络整理| 查看: 265

mybatis 源代码配置文件说明解析配置文件解析 mapper.xml 文件

mybatis 源代码

mybatis 的使用非常的简单,简单的加载一下配置文件即可实现对数据库的 curd 操作,如下所示

public class App { public static void main(String[] args) throws Exception { String resource = "config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream, null, null); try (SqlSession sqlSession = factory.openSession(true)) { int statement = sqlSession.insert("statement", "12"); } }}

在这个简单的示例代码的背后,mybatis 为我们做了些什么,怎么做的。

配置文件说明 加载并解析配置文件

配置文件分为2部分,分别是 configuration.xml 和 mapper.xml 部分,核心代码

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream, null, null);

简单一行即可实现 xml 的解析。内部解析顺序如下

获取 configuration.xml (不一定是这个名字)解析 configuration.xml 获取 Configuration 对象分别解析 configuration.xml 里边的 properties , settings typeAliases plugins objectFactory objectWrapperFactory reflectorFactory environments databaseIdProvider typeHandlers解析 mapper.xml 集合

第三步的一般只会使用 typeAliases 一项,其他的基本上使用默认设置或者Spring整和之后的设置

解析配置文件

解析代码 —> SqlSessionFactoryBuilder#build

public SqlSessionFactory build(Reader reader, @Nullable String environment, @Nullable Properties properties) { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); Configuration configuration = parser.parse(); //核心 return build(configuration);}

核心代码 ——> XMLConfigBuilder#parse

public Configuration parse() { //获取root节点 configuration并解析 XNode xNode = parser.evalNode("/configuration"); parseConfiguration(xNode); return configuration;}

解析节点配置信息,XMLConfigBuilder#parseConfiguration , 解析之前需要说明—mybatis 的Configuration对象是全局唯一的,解析配置文件本质是为了获取 Configuration 实例对象,即 使用xml中的节点信息去填充 Configuration 实例中对应的字段 ,例如别名注册,其实就是一个Map。

private void parseConfiguration(XNode root) { try { //先解析properties文件,和Spring整合之后我还没有看到使用的,我司主要使用xbatis+mysql //基本不用,maven的穿透更加好用 propertiesElement(root.evalNode("properties")); //解析settings配置,我是一般没有用 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); //别名,这个是需要的,简化后续的statement,简单 typeAliasesElement(root.evalNode("typeAliases")); //插件,执行拦截使用,特殊需求可能会用到 pluginElement(root.evalNode("plugins")); //一下节点,一般没有使用过 objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //解析mappers节点,mybatis核心 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }

解析别名节点

private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { //package 扫描包下的bean,进行注册 if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { //单独注册 String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); Class clazz = Resources.classForName(type); if (alias == null) { // this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); , 填充 Configuration 的 typeAliasRegistry 字段 typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } } }}

解析 mapper.xml 文件

解析 mapper.xml 这里以 mapper resource = “xxxxx” 为例

private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //package if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); //扫描 mapper } else { //resource,url class 只能同时存在一个 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); //读取xx_mapper.xml InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //解析xxx/mapper.xml mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class mapperInterface = Resources.classForName(mapperClass); //使用接口的模式,xxxMapper configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } }}

开始解析

public void parse() { //查看是不是已经被加载过了 if (!configuration.isResourceLoaded(resource)) { //解析mapper root节点,以/开头,解析基本的 configurationElement(parser.evalNode("/mapper")); //添加到loadedResources中,set集合 configuration.addLoadedResource(resource); //搭建命名空间 namespace 需要唯一 bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }

解析 mapper.xml 的属性

/** * 解析过程还是和configuration差不多,一个节点一个节点解析 */ private void configurationElement(XNode context) { try { //获取namespace属性 , namespace 不能为null的,也不能为empty的 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 节点 cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select*,+*表示可多个 //当前mapper设置namespace builderAssistant.setCurrentNamespace(namespace); //缓存引用,和cache两个节点使用的都不多,可以说基本没用过 cacheRefElement(context.evalNode("cache-ref")); //缓存 cacheElement(context.evalNode("cache")); //主要使用的不多,目前见到的是少部分使用场景,1、获取parameterMap节点群,>=0个节点,2、解析节点 parameterMapElement(context.evalNodes("/mapper/parameterMap")); //用的最多,也是mybatis的核心 resultMapElements(context.evalNodes("/mapper/resultMap")); //sql,必使用,动态sql依赖 sqlElement(context.evalNodes("/mapper/sql")); //curd 4个节点,绑定使用 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }

解析resultMap

private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception { //正在解析那个mapper文件,如果发生异常,将抛出这个ErrorContext中的内容,这种思路可以学习 ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); //resultMap 没有id会自动产生一个,强烈建议设置一个,dtd文件的验证一般都是有id属性的,确保万无一失,xbatis团队还是加了这个 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); //type类型是必须的,应该是dtd改了,为了兼容以前的情况加上了这套逻辑 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); //是否继承了哪个resultMap,复用 String extend = resultMapNode.getStringAttribute("extends"); //是否自动映射 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); Class typeClass = resolveClass(type); Discriminator discriminator = null; List resultMappings = new ArrayList(additionalResultMappings); //所有字节点 List resultChildren = resultMapNode.getChildren(); //constructor?,id*,result*,association*,collection*, discriminator? for (XNode resultChild : resultChildren) { //构造器 if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } //discriminator else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List flags = new ArrayList(); //id if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } //核心还是搭建ResultMapping,一个resultMapping的节点 对应了 一个ResultMapping的实例 ResultMapping resultMapping = buildResultMappingFromContext(resultChild, typeClass, flags); resultMappings.add(resultMapping); } } //一个完整的 resultMapping 就搭建完成了 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }

buildResultMappingFromContext 方法

/** resultMap 所有的节点属性都在这里了 * @param context resultMapping 字节点, * @param resultType resultMapping 对应的类型,这个类型一般都是简化过的 * @param flags flags */ private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception { String property; //有没有构造器,目前从我工作角度来看,几乎没有使用到构造器的地方,但是肯定有某些场景是使用这个的 if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name"); } else { //property 节点 property = context.getStringAttribute("property"); } //改属性对应的类名,获取所有的属性 String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); String nestedResultMap = context.getStringAttribute("resultMap",processNestedResultMappings(context, Collections.emptyList())); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); Class javaTypeClass = resolveClass(javaType); @SuppressWarnings("unchecked") Class> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); //除非有什么特殊的不行的业务需求,才需要加入自定义的类型处理器,最终还是放到 configuration 实例当中 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }

解析 4个 curd 节点

//解析curd节点,属于解析当中最为复杂的部 public void parseStatementNode() { //获取节点id String id = context.getStringAttribute("id"); //数据库标示,99的情况是nul String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } //-----------------节点属性解析 Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); //parameterMap parameterType 这两个我用起来一直不知道他们的区别是什么 String parameterMap = context.getStringAttribute("parameterMap"); //map 已经被移除了,但是type还是用的比较多的,例如常用的hashmap,map,list,array String parameterType = context.getStringAttribute("parameterType"); Class parameterTypeClass = resolveClass(parameterType); //同理还有这两个,区分度要大一些,但是新人还是会迷糊,虽然xbatis的文档不错 String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); // LanguageDriver langDriver = getLanguageDriver(lang); Class resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); //----------------节点属性解析 //---------------节点数据 String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //是否刷新缓存 是否使用缓存 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); //---------------- //----------------解析前先处理include子节点 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); //Node includeParser.applyIncludes(context.getNode()); //解析 selectKey节点 processSelectKeyNodes(id, parameterTypeClass, langDriver); //解析 xml/annotation 获取sql SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); //-----结果集 String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); //-----结束 KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }

到达这一步,mybatis xml文件就解析完成了,基本上只大概的谈了一下一些基本的属性的解析,不过只要能够理解 mybatis 的解析过程,后续的执行还是蛮好理解的,而且 mybatis 的源代码相对来说还是较为简单的。sql 节点没有说明,因为这里还是和设置参数结合起来讲述会简单一下,如果直接过一篇,反而没有多大的效果。

2018-10-12



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有